/*
 * Copyright (C) 2008 Peter Kling
 * 
 * This file is part of 'A Kind of Billiard', a graphical billiard simulation.
 * 
 * 'A Kind of Billiard' is free software: you can redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 * 
 * 'A Kind of Billiard' is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with 'A Kind of Billiard'. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package de.akob.billiard;

import javax.media.opengl.GL;

import de.akob.mathematics.MathMatrix;
import de.akob.mathematics.MathVector;
import de.akob.mathematics.Mathematics;
import de.akob.misc.OpenGLDrawable;
import de.akob.modeling.GLModel;
import de.akob.modeling.WavefrontObjLoader;

/**
 * A cue indicator that is used to visualize several information of the cue ball of a billiard table. This standard
 * implementation uses a arrow model that is drawn at the cue balls position. Its direction and length indicate the
 * corresponding properties of the next cue stroke.
 */
public class CueIndicator implements OpenGLDrawable {
	// model data
	public static final String MODEL_CUEINDICATOR = "models/billiard/cueIndicator.obj";
	
	// billiard table of this indicator
	private BilliardTable      billiardTable;
	private GLModel            model;
	
	// miscellaneous data
	public final double        CUEVELOCITY_MIN    = 0.1;
	public final double        CUEVELOCITY_MAX    = 4.0;
	public final double        CUEVELOCITY_STEP   = 0.1;
	private double             cueAngleH          = 0.0;
	private double             cueAngleV          = 0.0;
	private MathVector         cueRotAxisH        = null;
	private MathVector         cueRotAxisV        = null;
	private double             cueVelocity        = 0.0;
	
	/**
	 * Standard constructor.
	 */
	public CueIndicator(BilliardTable billiardTable) {
		this.billiardTable = billiardTable;
	}
	
	/**
	 * Decrease the velocity the cue ball will be played with.
	 */
	public void decreaseCueVelocity() {
		cueVelocity = Math.max(cueVelocity - CUEVELOCITY_STEP, CUEVELOCITY_MIN);
	}
	
	/**
	 * Draws the cue indicator.
	 */
	public void draw(GL gl) {
		MathVector cueBall_velocity = billiardTable.getCueBall().getVelocity();
		if (cueBall_velocity.norm() > 0.25)
			return;
		MathVector cueBall_position = billiardTable.getCueBall().getPosition();
		double cueBall_radius = Billiard.DBU_POOLBALL_RADIUS;
		double table_height = billiardTable.getHeight();
		
		gl.glColor4d(1.0, 1.0, 1.0, 1.0);
		gl.glPushMatrix();
		gl.glTranslated(cueBall_position.get(1), table_height + cueBall_radius + cueBall_position.get(2),
		        cueBall_position.get(3));
		gl.glRotated(cueAngleV, cueRotAxisV.get(1), cueRotAxisV.get(2), cueRotAxisV.get(3));
		gl.glRotated(cueAngleH, cueRotAxisH.get(1), cueRotAxisH.get(2), cueRotAxisH.get(3));
		gl.glTranslated(-1.25 * cueBall_radius, 0., 0.);
		gl.glScaled(1. + (cueVelocity - CUEVELOCITY_MIN) / (CUEVELOCITY_MAX - CUEVELOCITY_MIN), 1., 1.);
		model.draw(gl);
		gl.glPopMatrix();
	}
	
	/**
	 * Returns the velocity vector the ball will have after a cue stroke.
	 */
	public MathVector getCueStrokeVelocity() {
		MathMatrix rotMatrixH = Mathematics.getRotationMatrix3D(Math.PI * cueAngleH / 180., cueRotAxisH);
		MathMatrix rotMatrixV = Mathematics.getRotationMatrix3D(Math.PI * cueAngleV / 180., cueRotAxisV);
		MathVector velocityVector = Mathematics.mul(Mathematics.getUnitVector(3, 1), cueVelocity);
		velocityVector = Mathematics.mul(rotMatrixH, velocityVector);
		velocityVector = Mathematics.mul(rotMatrixV, velocityVector);
		return velocityVector;
	}
	
	/**
	 * Increases the velocity the cue ball will be played with.
	 */
	public void increaseCueVelocity() {
		cueVelocity = Math.min(cueVelocity + CUEVELOCITY_STEP, CUEVELOCITY_MAX);
	}
	
	/**
	 * Initializes the cue indicator.
	 */
	public void init(GL gl) {
		model = WavefrontObjLoader.loadObject(gl, MODEL_CUEINDICATOR);
		reset();
	}
	
	/**
	 * Resets the cue indicator (direction and velocity the ball will be played with).
	 */
	public void reset() {
		cueRotAxisH = Mathematics.getUnitVector(3, 2);
		cueRotAxisV = Mathematics.getUnitVector(3, 3);
		cueVelocity = 1.0;
		cueAngleH = 0.0;
		cueAngleV = 0.0;
	}
	
	/**
	 * Increases the angles for horizontally and vertically rotations of the cue by the given values.
	 */
	public void rotateCue(double angleH, double angleV) {
		cueAngleH += angleH;
		cueAngleV += angleV;
		
		// rotate the rotation axis for vertically rotation
		cueRotAxisV = Mathematics.mul(Mathematics.getRotationMatrix3D(Math.PI * angleH / 180., cueRotAxisH),
		        cueRotAxisV);
	}
	
	/**
	 * Rotates the cue some degree 'downwards'. Using this method you can rotate the cue such that the ball is played
	 * upwards up to some maximum angle.
	 */
	public void rotateCue_down() {
		rotateCue(0., 5.);
	}
	
	/**
	 * Rotates the cue some degree around the y-axis in mathematical positive sense.
	 */
	public void rotateCue_left() {
		rotateCue(5., 0.);
	}
	
	/**
	 * Rotates the cue some degree around the y-axis in mathematical negative sense.
	 */
	public void rotateCue_right() {
		rotateCue(-5., 0.);
	}
	
	/**
	 * Rotates the cue some degree 'upwards'. Using this method you can rotate the cue such that the ball is played
	 * downwards up to some maximum angle.
	 */
	public void rotateCue_up() {
		rotateCue(0., -5.);
	}
	
	/**
	 * Sets the velocity the cue ball will have after the next stroke with the cue.
	 */
	public void setCueVelocity(double value) {
		cueVelocity = value;
	}
}
